package com.huangxunyi.GUET;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * //=======================================================
 * //		          .----.
 * //		       _.'__    `.
 * //		   .--(^)(^^)---/!\
 * //		 .' @          /!!!\
 * //		 :         ,    !!!!
 * //		  `-..__.-' _.-\!!!/
 * //		        `;_:    `"'
 * //		      .'"""""`.
 * //		     /,  ya ,\\
 * //		    //狗神保佑\\
 * //		    `-._______.-'
 * //		    ___`. | .'___
 * //		   (______|______)
 * //=======================================================
 * Created by huang on 2017/2/25.
 */
public class IpClient {
    public static final String SERVER_NO_RESPONSE = "服务器无响应";
    public static final String NETWORK_CONNECTION_FAILURE = "网络连接失败";
    public static final String NEVER_LOGIN = "您还没有登录";
    public static final String NOT_ENOUGH_MONEY = "您的钱不够了";
    public static final String NEVER_SET_INFO = "您还没有设置用户名和密码";

    private static IpClient instance;

    private final static String SERVER_IP = "172.16.1.1";
    private final static int SERVER_CONTROL_PORT = 5300;
    private final static int SERVER_REFRESH_PORT = 5301;
    private final static int HOST_CONTROL_PORT = 5200;
    private final static int HOST_REFRESH_PORT = 5201;

    private final static int TIME_SOCKET_TIMEOUT = 3 * 1000;
    public final static int TIME_REFRESH_ONLINE_INTERVAL = 15 * 1000;

    private DatagramSocket cmdSocket;
    private DatagramSocket refreshSocket;
    private InetAddress ServerAddress;

    private ContralCmd mContralCmd;
    private RefreshCmd mRefreshCmd;

    private String username;
    private String password;

    private double flow;
    private double money;

    private boolean isLogin = false;
    private boolean isSetUserInfo = false;

    public synchronized static IpClient getInstance() {
        if (instance == null) {
            instance = new IpClient();
        }
        return instance;
    }

    private IpClient() {
        initNet();
    }

    private void initNet() {
        try {
            cmdSocket = new DatagramSocket(HOST_CONTROL_PORT);
            refreshSocket = new DatagramSocket(HOST_REFRESH_PORT);
            cmdSocket.setSoTimeout(TIME_SOCKET_TIMEOUT);
            refreshSocket.setSoTimeout(TIME_SOCKET_TIMEOUT);
            ServerAddress = InetAddress.getByName(SERVER_IP);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }


    public synchronized void setUserInfo(String username, String password) {
        mContralCmd = new ContralCmd(username, password);
        mRefreshCmd = new RefreshCmd(username, 0);
        this.username = username;
        this.password = password;
        isSetUserInfo = true;
        System.out.println(username + "   " + password);
    }

    public synchronized String getIP() throws RefreshFaileException {
        try {
            byte[] packet = sendTo(refreshSocket,
                    RefreshCmd.getRefreshGetNewsPacket(), ServerAddress,
                    SERVER_REFRESH_PORT);
            RefreshPacketModel model = RefreshCmd
                    .checkRefreshGetNewsReply(packet);
            if (model == null) {
                throw new RefreshFaileException(SERVER_NO_RESPONSE);
            }
            return model.getStr4();
        } catch (IOException e) {
            throw new RefreshFaileException(NETWORK_CONNECTION_FAILURE);
        }
    }

    public synchronized String login() throws LoginFaileException {
        if (!isSetUserInfo) {
            throw new LoginFaileException(NEVER_SET_INFO);
        }
        byte[] packet;
        try {
            packet = sendTo(cmdSocket, mContralCmd.getLoginUseridPacket(),
                    ServerAddress, SERVER_CONTROL_PORT);

            if (mContralCmd.checkLoginUseridReply(packet)) {
                byte[] packet2 = sendTo(cmdSocket,
                        mContralCmd.getLoginPasswordPacket(), ServerAddress,
                        SERVER_CONTROL_PORT);
                CMDPacketModel model = mContralCmd
                        .checkLoginPasswordReply(packet2);
                if (model == null) {
                    throw new LoginFaileException(SERVER_NO_RESPONSE);
                }
                if (model.getResult() != 0) {
                    throw new LoginFaileException(model.getStr4());
                }
                mRefreshCmd.setRefrshKey(mContralCmd.getKey());
                isLogin = true;
                refresh0a();
                return model.getStr4();
            } else {
                throw new LoginFaileException(SERVER_NO_RESPONSE);
            }
        } catch (IOException e) {
            throw new LoginFaileException(NETWORK_CONNECTION_FAILURE);
        }
    }

    public synchronized String logout() throws LogoutFaileException {
        if (!isLogin) {
            throw new LogoutFaileException(NEVER_LOGIN);
        }
        byte[] packet;
        try {
            packet = sendTo(cmdSocket, mContralCmd.getLogoutUseridPacket(),
                    ServerAddress, SERVER_CONTROL_PORT);

            if (mContralCmd.checkLogoutUseridReply(packet)) {
                byte[] packet2 = sendTo(cmdSocket,
                        mContralCmd.getLogoutPasswordPacket(),
                        ServerAddress, SERVER_CONTROL_PORT);
                CMDPacketModel model = mContralCmd
                        .checkLogoutPasswordReply(packet2);
                if (model == null) {
                    throw new LogoutFaileException(SERVER_NO_RESPONSE);
                }
                if (model.getResult() != 0) {
                    throw new LogoutFaileException(model.getStr4());
                }
                mContralCmd = null;
                mRefreshCmd = null;
                isLogin = false;
                return model.getStr4();
            } else {
                throw new LogoutFaileException(SERVER_NO_RESPONSE);
            }
        } catch (IOException e) {
            throw new LogoutFaileException(NETWORK_CONNECTION_FAILURE);
        }
    }

    private synchronized boolean refresh0a() {
        byte[] packet;
        try {
            packet = sendTo(refreshSocket, mRefreshCmd.getRefresh0APacket(),
                    ServerAddress, SERVER_REFRESH_PORT);
            if (!mRefreshCmd.checkRefresh0AReply(packet)) {
                return false;
            }
        } catch (IOException e) {
            return false;
        }
        return true;

    }

    public synchronized String refreshOnline() throws RefreshFaileException {
        if (!isLogin) {
            throw new RefreshFaileException(NEVER_LOGIN);
        }
        byte[] packet;
        try {
            packet = sendTo(refreshSocket,
                    mRefreshCmd.getRefreshOnlinePacket(), ServerAddress,
                    SERVER_REFRESH_PORT);

            RefreshPacketModel model = mRefreshCmd
                    .checkRefreshOnlineReply(packet);
            if (model == null) {
                throw new RefreshFaileException(SERVER_NO_RESPONSE);
            }
            flow = model.getFlow();
            money = model.getMoney();
            if (money < 0.2) {
                throw new RefreshFaileException(NOT_ENOUGH_MONEY);
            }
            return model.getMsg();
        } catch (IOException e) {
            throw new RefreshFaileException(NETWORK_CONNECTION_FAILURE);
        }
    }

    public double getFlow() {
        return flow;
    }

    public double getMoney() {
        return money;
    }

    public void setLogin(boolean isLogin) {
        this.isLogin = isLogin;
    }

    public boolean isLogin() {
        return isLogin;
    }

    public boolean isSetUserInfo() {
        return isSetUserInfo;
    }

    private synchronized byte[] sendTo(DatagramSocket sockets, byte[] data,
                                       InetAddress address, int port) throws IOException {
        DatagramPacket packet = new DatagramPacket(data, data.length, address,
                port);

        byte[] resultBuf = new byte[data.length];
        DatagramPacket receive = new DatagramPacket(resultBuf, resultBuf.length);

        sockets.send(packet);
        try {
            sockets.receive(receive);
            if (!receive.getAddress().equals(address)) {
                throw new IOException("Received packet from an umknown source");
            }

        } catch (InterruptedIOException e) {
//            e.printStackTrace();
        }
        return receive.getData();
    }

    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }


    private String getInfoFormResultCode(int result) {
        String info = null;
        switch (result) {
            case 0x1:
                info = "your ip needn\'t use ipclient.";
                break;
            case 0xa:
                info = "your account is expired.";
                break;
            case 0xb:
                info = "your account is disabled.";
                break;
            case 0x14:
                info = "your account has not enough money.";
                break;
            case 0x15:
                info = "your accont has not available hours in this month.";
                break;
            case 0x16:
                info = "your account has not available flow in this mouth.";
                break;
            case 0x19:
                info = "your account cannot be used in this IP.";
                break;
            case 0x1e:
                info = "your account cannot be used in this time.";
                break;
            case 0x1f:
                info = "please dial later.";
                break;
            case 0x20:
                info = "too many users are using this account now.";
                break;
            case 0x21:
                info = "ipclient cannot be used for your account.";
                break;
            case 0x22:
                info = "please dial later.";
                break;
            case 0x63:
                info = "userid or password error.";
                break;
            default:
                info = "error! not such result code!";
                break;
        }
        return info;
    }

}

class LoginFaileException extends Exception {

    private String message;
    private String time;

    public LoginFaileException(String string) {
        super(string);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        time = df.format(new Date());
        message = string;
        printStackTrace();
    }

    @Override
    public String getMessage() {
        return message + "    time:" + time;
    }

    public String getInfo() {
        return message;
    }
}

class RefreshFaileException extends Exception {
    private String message;
    private String time;

    public RefreshFaileException(String string) {
        super(string);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        time = df.format(new Date());
        message = string;
        printStackTrace();
    }

    @Override
    public String getMessage() {
        return message + "    time:" + time;
    }

    public String getInfo() {
        return message;
    }
}

class LogoutFaileException extends Exception {
    private String message;
    private String time;

    public LogoutFaileException(String string) {
        super(string);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        time = df.format(new Date());
        message = string;
        printStackTrace();
    }

    @Override
    public String getMessage() {
        return message + "    time:" + time;
    }

    public String getInfo() {
        return message;
    }
}
